#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>



#define ISspace(x) isspace((int)x)//空白返回非0

#define SERVER_STRING "Server: myhttpd/0.1.0\r\n"//定义server

void* accept_request(void *);//接受请求
void bad_request(int);//无效请求
void cat(int,FILE*);
void cannot_execute(int);
void error_die(const char *);
void execute_cgi(int,const char *,const char *,const char *);
int get_line(int, char *,int);
void not_found(int);
void server_file(int,const char *);
int startup(u_short *);
void unimplemented(int);


/***********************************************/
/*		void error_die(const char *)
 *		perror
 *
 */
/***********************************************/
void error_die(const char *sc)
{
	perror(sc);
	exit(1);
}

/*************************************************/
/*	send 400
 *
 * void bad_request(int client)
 */
/*************************************************/

void bad_request(int client)
{
	char buf[1024];

	//send 400

	sprintf(buf,"HTTP/1.0 400 BAD REQUEST\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"Content-Type: text/html\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"<P>Your browser sent a bad request,");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"such as a POST without a Content-Length.\r\n");
	send(client,buf,strlen(buf),0);
}

/**************************************************/
/*	void  not_found(int client)
 *   
 *	//返回404
 */
/*************************************************/

void not_found(int client)
{
	char buf[1024];
	//ret 404
	sprintf(buf,"HTTP/1.0 404 NOT FOUND\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,SERVER_STRING);
	send(client,buf,strlen(buf),0);
	sprintf(buf,"Content-Type: text/html\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"<HTML><TITLE>Not Found<TITLE>\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"<BODY><P>The server could not fulfill\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"your request because the resource specified\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"is unavailable or nonexistent\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"</BODY></HTML>\r\n");
	send(client,buf,strlen(buf),0);

}
/************************************************/
/*	void cat(int client,FILE * resource)
 *	
 *	获得指定文件下的内容发给客户端
 */
/************************************************/

void cat(int client,FILE * resource)
{
	char buf[1024];
	fgets(buf,sizeof(buf),resource);	
	while(!feof(resource))
	{
		send(client,buf,strlen(buf),0);
		fgets(buf,sizeof(buf),resource);
	}
}

/*************************************************/
/*	void headers(int client,const char *filename)
 *	
 *	发送200 响应报文
 */
/*************************************************/

void headers(int client,const char *filename)
{
	char buf[1024];
	(void)filename;//使用文件名来确定类型

	// send 200
	sprintf(buf,"HTTP/1.0 200 ok\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,SERVER_STRING);
	send(client,buf,strlen(buf),0);
	sprintf(buf,"Content-Type: text/html\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"\r\n");
	send(client,buf,strlen(buf),0);

}

/*************************************************/
/*
 *	void serve_file(int client,const char *filename)
 *
 *	将请求文件发送给浏览器客户端
 *
 */
/*************************************************/

void server_file(int client,const char *filename)
{
	FILE *resource = NULL;
	int numchars = 1;
	char buf[1024];


	buf[0] = 'A';
	buf[1] = '\0';
	while((numchars > 0) && strcmp("\n",buf))
		numchars = get_line(client,buf,sizeof(buf));

	//open file
	resource = fopen(filename,"r");
	if(resource == NULL)
		not_found(client);
	else
	{
		headers(client,filename);

		cat(client,resource);
	}
	fclose(resource);
}

/***************************************************/
/*	int get_line(int sock,char *buf,int size)
 *	
 *	解析一行HTTP报文
 *
 * 不管原行是\r\n还是\n\r都变成  \n\0
 */
/****************************************************/

int get_line(int sock,char *buf,int size)
{
	int i = 0;
	char c = '\0';
	int n;
	
	//每行的结尾都是\r\n

	while((i < size -1) && (c != '\n'))
	{	
		n = recv(sock,&c,1,0);
		if(n > 0)
		{
			if(c == '\r')
			{
				n = recv(sock,&c,1,MSG_PEEK);//MSG_PEEK 探测下一个字符是不是'\n'
				if((n>0) && (c == '\n'))//一行结束
					recv(sock,&c,1,0);
				else
					c = '\n';//换行替换回车，当一行返回
			}
			buf[i] = c;
			i++;
		}
		else
		{
			c = '\n';
		}
	}

	buf[i] = '\0';//返回读取的字节数，包括'\0'
	return i;
}
		



/***************************************************/
/*	void unimplemented(int)
 *		send 501
 *  未实现对应方法    
 *	响应报文的构建
 */
/****************************************************/
void unimplemented(int client)
{
	char buf[1024];
	//发送501说明服务器对于方法还没有实现
	sprintf(buf,"HTTP/1.0 501 Method Not Implemented\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,SERVER_STRING);
	send(client,buf,strlen(buf),0);
	sprintf(buf,"Content-Type: text/html\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"<HTML><HEAD><TITLE>Method Not Implemented\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"</TITLE></HEAD>\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"<BODY><P>HTTP request method not supported.\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"</BODY></HTML>\r\n");
	send(client,buf,strlen(buf),0);
}

/*****************************************************/
/*	处理客户端发来的HTTP请求报文
 *
 *	func_name	accept_request
 *
 *	param	    int client
 *
 */
/*****************************************************/
void* accept_request(void * client_)
{
	int client = (int)client_;
	
	char buf[1024];
	int numchars;
	char method[255];//保存HTTP请求方法，GET or POST
	char url[255];//状态行中的URL字段
	char path[512];//状态行中指定服务器上的路径
	size_t i,j;
	struct stat st;//stat struct
	int cgi = 0;//become 1 if server decides is a CGI program

	char *query_string = NULL;// GET 请求'?'之后参数

	//获取一行的HTTP报文
	numchars = get_line(client,buf,sizeof(buf));
	

	i = 0,j = 0;
	while(!ISspace(buf[j]) && (i<sizeof(method) -1))
	{
		method[i] = buf[j];
		i++;
		j++;
	}
	method[i] = '\0';//方法已经保存到method数组里面

	if(strcasecmp(method,"GET") && strcasecmp(method,"POST"))
	{
		//既不是GET也不是POST才会进入这个条件
		unimplemented(client);
		return NULL ;
	}

	if(strcasecmp(method,"POST") == 0)
		cgi = 1;
	i = 0;
	while(ISspace(buf[j]) && (j<sizeof(buf)))
		j++;//跳过空白字符
		//继续读取URL
	while(!ISspace(buf[j]) && (i<sizeof(url) -1) &&(j<sizeof(buf)))
	{
		url[i] = buf[j];
		i++;
		j++;
	}
	url[i] = '\0';//url已经保存在URL数组里

	//如果是GET请求，URL后面可能会带参数，参数？开始，&分隔，key=value 形式组织,那我们就要采取动态请求cgi
	if(strcasecmp(method,"GET") == 0)
	{
		query_string = url;
		while((*query_string != '?') && (*query_string != '\0'))//往后移动指针找到'?'的位置
			query_string++;
		if(*query_string == '?')//get '?' position
		{
			cgi = 1;
			*query_string = '\0';//把？变成结尾，分离参数
			query_string++;//指向参数
		}
	}
	


	//到此请求行解析完成

	//将URL中的路径格式化到path中
	sprintf(path,"htdocs%s",url);

	//如果path只是一个目录'/',默认的首页为index.html
	if(path[strlen(path)-1] == '/')
		strcat(path,"index.html");

	//stat通过filename返回文件信息

	if(stat(path,&st) == -1)
	{//如果读取的文件不存在，不断的读取剩下的请求信息
		while((numchars > 0) && strcmp("\n",buf))
			numchars = get_line(client,buf,sizeof(buf));
		not_found(client);//网页不存在404
	}
	else
	{
		
		//如果path代表的是个目录
		if((st.st_mode & S_IFMT) == S_IFDIR)
			strcat(path,"/index.html");
		if((st.st_mode & S_IXUSR) ||
		   (st.st_mode & S_IXGRP) ||
		   (st.st_mode & S_IXOTH) )
			cgi = 1;
		if(!cgi)
			server_file(client,path);//将静态文件返回
		else
			execute_cgi(client,path,method,query_string);
	}

	close(client);//因为HTTP是面向无连接的，所以要关闭
	return NULL;
}

/*************************************************/
/*		void cannot_execute(int client)
 *
 *		send 500
 */
/**************************************************/

void cannot_execute(int client)
{
	char buf[1024];
	//send 500

	sprintf(buf,"HTTP/1.0 500 Inter Server Error\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"Content-Type: text/html\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"\r\n");
	send(client,buf,strlen(buf),0);
	sprintf(buf,"<P>Error prohibited CGI execution.\r\n");
	send(client,buf,strlen(buf),0);
}
/***************************************************/
/*
 *		cgi 动态解析
 *	void execute_cgi(int client,const char *path,const char *method,const char *query_string)
 *		param :env
 */
/**************************************************/

void execute_cgi(int client,const char *path,const char *method,const char *query_string)
{	

	char buf[1024];
	int cgi_output[2];
	int cgi_input[2];
	
	pid_t pid;

	int status;

	int i;

	char c;
	int numchars = 1;
	int content_length = -1;//服务器告知浏览器自己的响应报文长度
	buf[0]  = 'A';
	buf[1]  = '\0';//buf = "A";

	if(strcasecmp(method,"GET") == 0)
	{
		while((numchars > 0) && strcmp("\n",buf))
			numchars = get_line(client,buf,sizeof(buf));
	}
	else
	{
		//POST
		numchars = get_line(client,buf,sizeof(buf));
		while((numchars > 0) && strcmp("\n",buf))
		{	
			buf[15] = '\0';//截取"Content-Length:"
			if(strcasecmp(buf,"Content-Length:") == 0)
				content_length = atoi(&(buf[16]));//获取Content-Length 的值
			numchars = get_line(client,buf,sizeof(buf));
		}

		if(content_length == -1)
		{
			bad_request(client);
			return;
		}
	}
	
	sprintf(buf,"HTTP/1.0 200 OK\r\n");
	send(client,buf,strlen(buf),0);


	if(pipe(cgi_output) < 0)
	{	
		cannot_execute(client);//500
		return;
	}
	if(pipe(cgi_input) < 0)
	{
		cannot_execute(client);
		return;
	}

	if((pid = fork()) < 0)
	{
		cannot_execute(client);
		return ;
	}

	// fork出子进程同来运行cgi脚本
	if(pid == 0)
	{
		//env
		char meth_env[255];
		char query_env[255];
		char length_env[255];

		dup2(cgi_output[1],1);//stdout --> cgi_output[1]
		dup2(cgi_input[0],0);//stdin-->cgi_input[0]
		//cgi程序中用的是标准输入标准输出进行交互，现在重定向了
		close(cgi_output[0]);
		close(cgi_input[1]);

		sprintf(meth_env,"REQUEST_METHOD=%s",method);
		putenv(meth_env);
		if(strcasecmp(method,"GET") == 0)
		{	
			sprintf(query_env,"QUERY_STRING=%s",query_string);
			putenv(query_env);
		}
		else
		{
			//POST
			sprintf(length_env,"CONTENT_LENGTH=%d",content_length);
			putenv(length_env);
		}

		execl(path,path,NULL);

		exit(0);
	}
	else
	{
		//parent

		close(cgi_output[1]);
		close(cgi_input[0]);

		if(strcasecmp(method,"POST") == 0)
		{	
			for(i = 0;i<content_length;i++)
			{
				//开始读取POST 中的内容
				recv(client,&c,1,0);
				//将数据发送给CGI脚本
				write(cgi_input[1],&c,1);
			}
		}
		//读取CGI脚本返回的数据
		while(read(cgi_output[0],&c,1) > 0)
			send(client,&c,1,0);

		close(cgi_output[0]);
		close(cgi_input[1]);

		waitpid(pid,&status,0);
	}


}


/*********************************************************/
/*	function:	startup()
 *  param:      u_short* port
 *  affect：    建立和客户端的连接
 *              创建套接字
 *              绑定端口
 *              监听端口
 */
/**********************************************************/
int startup(u_short *port)
{
	int httpd = 0;
	struct sockaddr_in name;
	
	httpd = socket(PF_INET,SOCK_STREAM,0);
	if(httpd == -1)
		error_die("socket");
	memset(&name,0,sizeof(name));
	name.sin_family = AF_INET;
	name.sin_port = htons(*port);
	name.sin_addr.s_addr = htonl(INADDR_ANY);
	//bind
	if(bind(httpd,(struct sockaddr *)&name,sizeof(name)) < 0)
		error_die("bind");
	if(*port == 0)//动态分配一个端口
	{
		socklen_t namelen = sizeof(name);
		if(getsockname(httpd,(struct sockaddr*)&name,&namelen)== -1)//获取连接的客户端信息
			error_die("getsockname");
		*port = ntohs(name.sin_port);
	}

	//listen
	if(listen(httpd,5)<0)
		error_die("listen");
	return httpd;
}
















/**********************************************************/

int main()
{
	int server_sock = -1;
	u_short port = 0;

	int client_sock = -1;
	struct sockaddr_in client_name;
	socklen_t client_name_len = sizeof(client_name);
	pthread_t newthread;

	//启动server socket
	server_sock = startup(&port);
	
	printf("该服务器的端口为:%d\n",port);


	while(1)
	{
		//接受客户端的请求
		client_sock = accept(server_sock,(struct sockaddr *)&client_name,&client_name_len);

		if(client_sock == -1)
			error_die("accept");

		//连接一个客户端就生成一个线程来调用accept_request来处理用户发来的HTTP请求
		if(pthread_create(&newthread,NULL,accept_request,(void*)client_sock) != 0)
			perror("pthread_create)");
	}

	//关闭server sock
	close(server_sock);

	return 0;
}
